﻿using System;
using System.Collections.Generic;
using System.IO;
using MiscUtil.Conversion;
using MiscUtil.IO;

namespace DivaScript.X
{
    /// <summary>
    /// Project DIVA X script file.
    /// </summary>
    public class Script
    {
        /// <summary>
        /// Returns the number of parameters based on the input opcode.
        /// </summary>
        public static int GetParameterCount(Opcode opcode)
        {
            switch (opcode)
            {
                case Opcode.END: return 0;
                case Opcode.TIME: return 1;
                case Opcode.MIKU_MOVE: return 4;
                case Opcode.MIKU_ROT: return 2;
                case Opcode.MIKU_DISP: return 2;
                case Opcode.MIKU_SHADOW: return 2;
                case Opcode.TARGET: return 12;
                case Opcode.SET_MOTION: return 4;
                case Opcode.SET_PLAYDATA: return 2;
                case Opcode.EFFECT: return 6;
                case Opcode.FADEIN_FIELD: return 2;
                case Opcode.EFFECT_OFF: return 1;
                case Opcode.SET_CAMERA: return 6;
                case Opcode.DATA_CAMERA: return 2;
                case Opcode.CHANGE_FIELD: return 2;
                case Opcode.HIDE_FIELD: return 1;
                case Opcode.MOVE_FIELD: return 3;
                case Opcode.FADEOUT_FIELD: return 2;
                case Opcode.EYE_ANIM: return 3;
                case Opcode.MOUTH_ANIM: return 5;
                case Opcode.HAND_ANIM: return 5;
                case Opcode.LOOK_ANIM: return 4;
                case Opcode.EXPRESSION: return 4;
                case Opcode.LOOK_CAMERA: return 5;
                case Opcode.LYRIC: return 2;
                case Opcode.MUSIC_PLAY: return 0;
                case Opcode.MODE_SELECT: return 2;
                case Opcode.EDIT_MOTION: return 4;
                case Opcode.BAR_TIME_SET: return 2;
                case Opcode.SHADOWHEIGHT: return 2;
                case Opcode.EDIT_FACE: return 1;
                case Opcode.DUMMY: return 21;
                case Opcode.PV_END: return 0;
                case Opcode.SHADOWPOS: return 3;
                case Opcode.EDIT_LYRIC: return 2;
                case Opcode.EDIT_TARGET: return 5;
                case Opcode.EDIT_MOUTH: return 1;
                case Opcode.SET_CHARA: return 1;
                case Opcode.EDIT_MOVE: return 7;
                case Opcode.EDIT_SHADOW: return 1;
                case Opcode.EDIT_EYELID: return 1;
                case Opcode.EDIT_EYE: return 2;
                case Opcode.EDIT_ITEM: return 1;
                case Opcode.EDIT_EFFECT: return 2;
                case Opcode.EDIT_DISP: return 1;
                case Opcode.EDIT_HAND_ANIM: return 2;
                case Opcode.AIM: return 3;
                case Opcode.HAND_ITEM: return 3;
                case Opcode.EDIT_BLUSH: return 1;
                case Opcode.NEAR_CLIP: return 2;
                case Opcode.CLOTH_WET: return 2;
                case Opcode.LIGHT_ROT: return 3;
                case Opcode.SCENE_FADE: return 6;
                case Opcode.TONE_TRANS: return 6;
                case Opcode.SATURATE: return 1;
                case Opcode.FADE_MODE: return 1;
                case Opcode.AUTO_BLINK: return 2;
                case Opcode.PARTS_DISP: return 3;
                case Opcode.TARGET_FLYING_TIME: return 1;
                case Opcode.CHARA_SIZE: return 2;
                case Opcode.CHARA_HEIGHT_ADJUST: return 2;
                case Opcode.ITEM_ANIM: return 4;
                case Opcode.CHARA_POS_ADJUST: return 4;
                case Opcode.SCENE_ROT: return 1;
                case Opcode.EDIT_MOT_SMOOTH_LEN: return 2;
                case Opcode.PV_BRANCH_MODE: return 1;
                case Opcode.DATA_CAMERA_START: return 2;
                case Opcode.MOVIE_PLAY: return 1;
                case Opcode.MOVIE_DISP: return 1;
                case Opcode.WIND: return 3;
                case Opcode.OSAGE_STEP: return 3;
                case Opcode.OSAGE_MV_CCL: return 3;
                case Opcode.CHARA_COLOR: return 2;
                case Opcode.SE_EFFECT: return 1;
                case Opcode.CHARA_SHADOW_QUALITY: return 2;
                case Opcode.STAGE_SHADOW_QUALITY: return 2;
                case Opcode.COMMON_LIGHT: return 2;
                case Opcode.TONE_MAP: return 2;
                case Opcode.IBL_COLOR: return 2;
                case Opcode.REFLECTION: return 2;
                case Opcode.CHROMATIC_ABERRATION: return 3;
                case Opcode.STAGE_SHADOW: return 2;
                case Opcode.REFLECTION_QUALITY: return 2;
                case Opcode.PV_END_FADEOUT: return 2;
                case Opcode.CREDIT_TITLE: return 1;
                case Opcode.BAR_POINT: return 1;
                case Opcode.BEAT_POINT: return 1;
                case Opcode.RESERVE: return 9;
                case Opcode.PV_AUTH_LIGHT_PRIORITY: return 2;
                case Opcode.PV_CHARA_LIGHT: return 3;
                case Opcode.PV_STAGE_LIGHT: return 3;
                case Opcode.TARGET_EFFECT: return 11;
                case Opcode.FOG: return 3;
                case Opcode.BLOOM: return 2;
                case Opcode.COLOR_CORRECTION: return 3;
                case Opcode.DOF: return 3;
                case Opcode.CHARA_ALPHA: return 4;
                case Opcode.AUTO_CAPTURE_BEGIN: return 1;
                case Opcode.MANUAL_CAPTURE: return 1;
                case Opcode.TOON_EDGE: return 3;
                case Opcode.SHIMMER: return 3;
                case Opcode.ITEM_ALPHA: return 4;
                case Opcode.MOVIE_CUT: return 1;
                case Opcode.EDIT_CAMERA_BOX: return 112;
                case Opcode.EDIT_STAGE_PARAM: return 1;
                case Opcode.EDIT_CHANGE_FIELD: return 1;
                case Opcode.MIKUDAYO_ADJUST: return 7;
                case Opcode.LYRIC_2: return 2;
                case Opcode.LYRIC_READ: return 2;
                case Opcode.LYRIC_READ_2: return 2;
                case Opcode.ANNOTATION: return 5;
                case Opcode.STAGE_EFFECT: return 2;
                case Opcode.SONG_EFFECT: return 3;
                case Opcode.SONG_EFFECT_ATTACH: return 3;
                case Opcode.LIGHT_AUTH: return 2;
                case Opcode.FADE: return 2;
                case Opcode.SET_STAGE_EFFECT_ENV: return 2;
                case Opcode.COMMON_EFFECT_AET_FRONT: return 2;
                case Opcode.COMMON_EFFECT_AET_FRONT_LOW: return 2;
                case Opcode.COMMON_EFFECT_PARTICLE: return 2;
                case Opcode.SONG_EFFECT_ALPHA_SORT: return 3;
                case Opcode.LOOK_CAMERA_FACE_LIMIT: return 5;
                case Opcode.ITEM_LIGHT: return 3;
                case Opcode.CHARA_EFFECT: return 3;
                case Opcode.MARKER: return 2;
                case Opcode.CHARA_EFFECT_CHARA_LIGHT: return 3;
                case Opcode.ENABLE_COMMON_LIGHT_TO_CHARA: return 2;
                case Opcode.ENABLE_FXAA: return 2;
                case Opcode.ENABLE_TEMPORAL_AA: return 2;
                case Opcode.ENABLE_REFLECTION: return 2;
                case Opcode.BANK_BRANCH: return 2;
                case Opcode.BANK_END: return 2;
                case Opcode.VR_LIVE_MOVIE: return 2;
                case Opcode.VR_CHEER: return 2;
                case Opcode.VR_CHARA_PSMOVE: return 2;
                case Opcode.VR_MOVE_PATH: return 2;
                case Opcode.VR_SET_BASE: return 2;
                case Opcode.VR_TECH_DEMO_EFFECT: return 2;
                case Opcode.VR_TRANSFORM: return 2;
                case Opcode.GAZE: return 2;
                case Opcode.TECH_DEMO_GESUTRE: return 2;
                case Opcode.VR_CHEMICAL_LIGHT_COLOR: return 2;
                case Opcode.VR_LIVE_MOB: return 5;
                case Opcode.VR_LIVE_HAIR_OSAGE: return 9;
                case Opcode.VR_LIVE_LOOK_CAMERA: return 9;
                case Opcode.VR_LIVE_CHEER: return 5;
                case Opcode.VR_LIVE_GESTURE: return 3;
                case Opcode.VR_LIVE_CLONE: return 7;
                case Opcode.VR_LOOP_EFFECT: return 7;
                case Opcode.VR_LIVE_ONESHOT_EFFECT: return 6;
                case Opcode.VR_LIVE_PRESENT: return 9;
                case Opcode.VR_LIVE_TRANSFORM: return 5;
                case Opcode.VR_LIVE_FLY: return 5;
                case Opcode.VR_LIVE_CHARA_VOICE: return 2;
                default: break;
            }
            return -1;
        }

        /// <summary>
        /// Parses a binary X formatted *.dsc file
        /// and returns a PvCommand list of all contained functions.
        /// </summary>
        internal static List<PvCommand> ParseFile(string filePath, bool isBigEndian)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException();

            var commands = new List<PvCommand>();

            var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read) { Position = 0x48 };
            using (var br = isBigEndian ? new EndianBinaryReader(EndianBitConverter.Big, fs) : new EndianBinaryReader(EndianBitConverter.Little, fs))
            {
                int currTime = 0;
                Opcode opcode;
                do
                {
                    var currParam = new List<int>();
                    opcode = (Opcode)br.ReadUInt32();

                    if (!Enum.IsDefined(typeof(Opcode), opcode))
                        break;

                    for (int i = 0; i < GetParameterCount(opcode); i++)
                        currParam.Add(br.ReadInt32());

                    currTime = opcode == Opcode.TIME ? currParam[0] : currTime;

                    commands.Add(new PvCommand(currTime, opcode.ToString(), currParam));

                } while (fs.Position < fs.Length);
            }

            return commands;
        }
        
        internal static void WriteToFile(string outputPath, List<PvCommand> commands, bool isBigEndian)
        {
            var fs = new FileStream(outputPath, FileMode.Create);
            using (var bw = isBigEndian ? new EndianBinaryWriter(EndianBitConverter.Big, fs) : new EndianBinaryWriter(EndianBitConverter.Little, fs))
            {
                var magic = new char[4] { 'P', 'V', 'S', 'C' };

                for (byte i = 0; i < magic.Length; i++)
                    bw.Write((byte)magic[i]);

                for (byte i = 0; i < 0x11; i++)
                    bw.Write(0);

                foreach (var command in commands)
                {
                    if (!Enum.TryParse(command.Opcode, out Opcode opcode))
                        continue;

                    bw.Write((int)opcode);
                    for (int i = 0; i < GetParameterCount(opcode); i++)
                    {
                        if (i < command.Parameters.Count)
                        {
                            bw.Write(command.Parameters[i]);
                        }
                        else
                        {
                            bw.Write(0);
                        }
                    }
                }

                var eofc = new char[4] { 'E', 'O', 'F', 'C' };

                for (byte i = 0; i < eofc.Length; i++)
                    bw.Write((byte)eofc[i]);

                for (byte i = 0; i < 6; i++)
                    bw.Write(0);
            }
            fs.Dispose();
        }
    }
}
